#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN TELEPORT version 2

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For commercial software, please obtain a commercial license from THN Solutions LLC.

  + For free software, this library is licensed under GPL version 3. A summary of GPLv3 is
    listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt included
	with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Using directives
using System;
using System.Collections;
using System.Reflection;

using Thn;
using Thn.Reflection;
using Thn.Communication.Teleporting.Settings;
#endregion

namespace Thn.Communication.Teleporting
{
    /// <summary>
    /// Default implementation of ITeleporter
    /// </summary>
    public class Teleporter : ITeleporter
    {
        #region Fields
        /// <summary>
        /// Contains gateway objects
        /// <para>key   : string</para>
        /// <para>value : object</para>
        /// </summary>       
        Hashtable mGateways = new Hashtable();
        Reflector mReflector = new Reflector();
        #endregion

        #region Events
        /// <summary>
        /// Raised when teleport needs to transmit a request
        /// </summary>
        public event TeleportRequestHandler SendMessage;
        #endregion

        #region Properties

        #region Codec
        private ITextCodec mCodec;
        /// <summary>
        /// Gets/Sets encode-decoder
        /// </summary>
        public ITextCodec Codec
        {
            get { return mCodec; }
            set { mCodec = value; }
        }
        #endregion

        #region Return Error
        private bool mReturnErrors = false;
        bool mIsReturnErrorAssigned = false;
        /// <summary>
        /// Gets/Sets whether this teleporter should return the error message to the requesting interceptor.
        /// <para>When this property is not assigned, it uses TeleportSettings.ReturnErrors</para>
        /// </summary>
        public bool ReturnErrors
        {
            get
            {
                bool result = mReturnErrors;
                if (!mIsReturnErrorAssigned) result = TeleportSettings.ReturnErrors;
                #region Defensive tracing
                else
                {
                    //already assigned, use the assigned value.
                }
                #endregion

                return result;
            }
            set
            {
                mReturnErrors = value;
                mIsReturnErrorAssigned = true;
            }
        }
        #endregion

        #endregion

        #region ITeleporter Members

        #region Add Gateway
        /// <summary>
        /// Registers the target object for remote invoking
        /// </summary>
        public void AddGateway(string gateway, object value)
        {
            if (mGateways.ContainsKey(gateway)) mGateways.Remove(gateway);
            mGateways.Add(gateway, value);
        }
        #endregion

        #region Remove Gateway
        /// <summary>
        /// Unregister a specific gateway
        /// </summary>
        public void RemoveGateway(string gateway)
        {
            if (mGateways.ContainsKey(gateway)) mGateways.Remove(gateway);
        }
        #endregion

        #region Request
        /// <summary>
        /// Beams a request to the other gateway
        /// </summary>
        /// <param name="gateway">Name of target gateway</param>
        /// <param name="method">Name of methods</param>
        /// <param name="values">Arguments</param>
        /// <returns>Returned results</returns>
        [System.Diagnostics.DebuggerHidden]
        public object Request(string gateway, string method, params object[] values)
        {
            #region Tracing
            if (TeleportSettings.Tracing)
            {
                Log.Debug("Request - Gateway: {0} Method: {1} Values: {2}", gateway, method, values);
            }
            #endregion

            object result = null;

            string args = string.Empty;

            #region Encode values
            try
            {
                args = mCodec.Encode(values);
            }
            catch (Exception ex)
            {
                CodecException ce = new CodecException(CodecException.GetMessage_Encode(), ex);
                ce.Source = "CLIENT";
                throw ce;
            }
            #endregion

            if (SendMessage != null)
            {
                string returnedMessage = SendMessage(gateway, method, args);

                if ((returnedMessage != null) && (returnedMessage != ""))
                {
                    #region Decode Message
                    try
                    {
                        result = mCodec.Decode(returnedMessage);
                    }
                    catch (Exception ex)
                    {
                        CodecException ce = new CodecException(CodecException.GetMessage_Decode(), ex);
                        ce.Source = "CLIENT";
                        throw ce;
                    }
                    #endregion

                    #region Handle error returned from server

                    //Log.Information("Decoded result: {0}", result);
                    if (result is TeleportExceptionInfo)
                    {
                        TeleportExceptionInfo errorInfo = (TeleportExceptionInfo)result;
                        result = null;
                        Exception error = TeleportException.Build_FromServer(errorInfo);
                        throw error;

                        //TeleportException.Publish_FromServer(errorInfo);
                    }
                    #region Defensive tracing
                    else
                    {
                        //normal result, proceed normally
                    }
                    #endregion

                    #endregion
                }
            }
            #region Defensive tracing
            else
            {
                Exception ex = new Exception("No connector assigned");
            }
            #endregion

            return result;
        }
        #endregion

        #region Process Request
        /// <summary>
        /// Request has been received, execute it
        /// </summary>
        /// <param name="gateway">Name of target gateway</param>
        /// <param name="method">Name of methods</param>
        /// <param name="encodedArgs">Encoded arguments</param>
        /// <returns>Encoded results</returns>
        string ITeleporter.ProcessRequest(string gateway, string method, string encodedArgs)
        {
            #region Tracing
            //if (TeleportSettings.Tracing)
            //{
            //    Log.Debug("Received request - Gateway: {0} Method: {1} EncodedArgs: {2}", gateway, method, encodedArgs);
            //}
            #endregion

            string result = "";

            try
            {
                if ((gateway != null) && (method != null) && (gateway != "") && (method != ""))
                {
                    #region Decode Arguments

                    //decode arguments
                    object DecodedTmp = null;
                    object[] args = null;
                    if ((encodedArgs != null) && (encodedArgs != ""))
                    {
                        //try
                        {
                            DecodedTmp = mCodec.Decode(encodedArgs);
                        }
                        //catch (Exception ex)
                        //{
                        //    CodecException ce = new CodecException(CodecException.GetMessage_Decode(), ex);
                        //    throw ce;
                        //}

                        if (DecodedTmp.GetType().IsArray) args = (object[])DecodedTmp;
                        else args = new object[] { DecodedTmp };
                    }
                    #endregion

                    #region Process request

                    //process decoded request
                    object ProcessedValue = null;

                    //ask interceptor to process the request
                    ProcessedValue = ProcessDecodedRequest(gateway, method, args);

                    //encode result
                    if (ProcessedValue != null)
                    {
                        try
                        {
                            result = mCodec.Encode(ProcessedValue);
                        }
                        catch (Exception ex)
                        {
                            CodecException ce = new CodecException(CodecException.GetMessage_Encode(), ex);
                            throw ce;
                        }
                    }
                    #endregion

                }
                #region Defensive tracing
                else
                {
                    if ((gateway == null) || (gateway == "")) throw new Thn.Exceptions.NullArgumentException("Gateway");
                    else throw new Thn.Exceptions.NullArgumentException("Method");
                }
                #endregion
            }
            catch (Exception error)
            {
                #region Handle error

                error.Source = "SERVER";
                TeleportExceptionInfo errorInfo = null;

                //if (ReturnErrors)
                if (true)
                {
                    //create a new exception info and pass it back to the other gateway
                    if (error is TargetInvocationException)
                    {
                        if (error.InnerException != null) errorInfo = new TeleportExceptionInfo(error.InnerException);
                        else errorInfo = new TeleportExceptionInfo(error);
                    }
                    else errorInfo = new TeleportExceptionInfo(error);

                    result = mCodec.Encode(errorInfo);
                }
                #region Defensive tracing
                else
                {
#if DEBUG
                    TeleportException.Publish_FromServer(errorInfo);
#endif
                }
                #endregion

                #endregion
            }

            return result;
        }

        /// <summary>
        /// Request has been received, execute it. If Gateway is an actual object, Teleporter will
        /// use it to process request instead of looking up from its name
        /// </summary>
        /// <param name="gateway">Gateway object</param>
        /// <param name="method">Name of methods</param>
        /// <param name="encodedArgs">Encoded arguments</param>
        /// <returns>Encoded results</returns>
        string ITeleporter.ProcessRequest(object gateway, string method, string encodedArgs)
        {
            string result = "";

            try
            {
                if ((gateway != null) && (method != null) && (method != ""))
                {
                    #region Decode arguments

                    object DecodedTmp = null;
                    object[] args = null;
                    if ((encodedArgs != null) && (encodedArgs != ""))
                    {
                        //try
                        {
                            DecodedTmp = mCodec.Decode(encodedArgs);
                        }
                        //catch (Exception ex)
                        //{
                        //    CodecException ce = new CodecException(CodecException.GetMessage_Decode(), ex);
                        //    throw ce;
                        //}
                        if (DecodedTmp.GetType().IsArray) args = (object[])DecodedTmp;
                        else args = new object[] { DecodedTmp };
                    }
                    #endregion

                    #region Process request

                    //process decoded request
                    object ProcessedValue = null;

                    //ask interceptor to process the request
                    ProcessedValue = ProcessDecodedRequest(gateway, method, args);

                    //encode result
                    if (ProcessedValue != null)
                    {
                        try
                        {
                            result = mCodec.Encode(ProcessedValue);
                        }
                        catch (Exception ex)
                        {
                            CodecException ce = new CodecException(CodecException.GetMessage_Encode(), ex);
                            throw ce;
                        }
                    }

                    #endregion
                }
                #region Defensive tracing
                else
                {
                    if (gateway == null) throw new Thn.Exceptions.NullArgumentException("Gateway");
                    else throw new Thn.Exceptions.NullArgumentException("Method");
                }
                #endregion
            }
            catch (Exception error)
            {
                #region Handle error

                error.Source = "SERVER";
                TeleportExceptionInfo errorInfo = null;

                if (ReturnErrors)
                {
                    //create a new exception info and pass it back to the other gateway
                    if (error is TargetInvocationException)
                    {
                        if (error.InnerException != null) errorInfo = new TeleportExceptionInfo(error.InnerException);
                        else errorInfo = new TeleportExceptionInfo(error);
                    }
                    else errorInfo = new TeleportExceptionInfo(error);

                    result = mCodec.Encode(errorInfo);
                }
                #region Defensive tracing
                else
                {
#if DEBUG
                    TeleportException.Publish_FromServer(errorInfo);
#endif
                }
                #endregion

                #endregion
            }

            return result;
        }
        #endregion

        #endregion

        #region Process Decoded Request
        /// <summary>
        /// Request has been received, execute it
        /// </summary>
        /// <param name="gateway">Name of target gateway</param>
        /// <param name="method">Name of methods</param>
        /// <param name="values">Arguments</param>
        /// <returns>Returned results</returns>
        object ProcessDecodedRequest(string gateway, string method, params object[] values)
        {
            object result = null;

            if (mGateways.ContainsKey(gateway))
            {//Gateway found
                object gw = mGateways[gateway];

                //try to invoke method
                //result = Invoke(method, gateway, values);
                result = gw.GetType().InvokeMember(method, BindingFlags.InvokeMethod, null, gw, values);
                //result = mReflector.InvokeMethod(gw, method, values);
                //try
                //{
                //    result = Invoke(Method, Gateway, Values);
                //    //result = gw.GetType().InvokeMember(Method, BindingFlags.InvokeMethod, null, gw, Values);
                //}
                //catch (Exception ex)
                //{
                //    result = new Results(ex);
                //}
            }//Gateway found
            else
            {
                throw new TeleportException(gateway);
            }

            return result;
        }

        /// <summary>
        /// Request has been received, execute it
        /// </summary>
        /// <param name="gateway">Gateway object</param>
        /// <param name="method">Name of methods</param>
        /// <param name="values">Arguments</param>
        /// <returns>Returned results</returns>
        object ProcessDecodedRequest(object gateway, string method, params object[] values)
        {
            object result = null;

            //try to invoke method
            //result = Invoke(method, gateway, values);
            //result = gateway.GetType().InvokeMember(method, BindingFlags.InvokeMethod, null, gateway, values);
            mReflector.InvokeMethod(gateway, method, values);
            //try
            //{
            //    result = Invoke(Method, Gateway, Values);
            //    //result = Gateway.GetType().InvokeMember(Method, BindingFlags.InvokeMethod, null, Gateway, Values);
            //}
            //catch (Exception ex)
            //{
            //    result = new Results(ex);
            //}

            return result;
        }
        #endregion

        #region Invoke
        /// <summary>
        /// invoke a member dynamically. Supports multiple level such as object.sub.Method and property such as object.sub.MyProperty
        /// </summary>
        object Invoke(string member, object target, object[] args)
        {
            object result = null;
            Type tTarget = target.GetType();
            int idx = member.IndexOf(".");

            try
            {
                if (idx <= 0)
                {//member is in root, no sub level, process normally
                    result = tTarget.InvokeMember(member, BindingFlags.InvokeMethod | BindingFlags.GetProperty, null, target, args);
                }
                else
                {//has sub level
                    string[] names = member.Split('.');
                    object subObject = null;

                    //get the last sub-object
                    for (int i = 0; i <= names.Length - 2; i++)
                    {
                        MemberInfo mi = tTarget.GetMember(names[i])[0];
                        PropertyInfo pi = (PropertyInfo)mi;
                        subObject = pi.GetValue(target, null);

                        target = subObject;
                        tTarget = target.GetType();
                    }

                    //invoke member
                    result = subObject.GetType().InvokeMember(names[names.Length - 1], BindingFlags.GetProperty | BindingFlags.InvokeMethod, null, subObject, args);
                }//has sub level
            }
            catch (Exception error)
            {
                if (error is TargetInvocationException)
                {
                    if (error.InnerException != null) throw error.InnerException;
                    else throw new Exception("Could not invoke " + member);
                }
                else throw error;
            }

            return result;
        }
        #endregion

        #region To String
        /// <summary>
        /// Converts to display string.
        /// </summary>
        public override string ToString()
        {
            string result = this.GetHashCode().ToString();

            if (this == Teleport.Teleporter)
            {
                result += " (Default teleporter)";
            }
            #region Defensive tracing
            else
            {
                //not the same
            }
            #endregion

            return result;
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Default constructor 
        /// </summary>
        public Teleporter()
        {
            mCodec = new XmlCodec();
            TypeResolver.Register(typeof(TeleportExceptionInfo));
        }
        #endregion
    }

    /// <summary>
    /// Provides this handler for a teleporter to transport request
    /// </summary>
    /// <param name="gateway">Name of gateway</param>
    /// <param name="method">Name of method or Property</param>
    /// <param name="encodedArgs">Serialized arguments</param>
    public delegate string TeleportRequestHandler(string gateway, string method, string encodedArgs);
}
